package com.appwithheart.toolkit;

import java.io.File;
import java.util.ArrayList;

import android.os.FileObserver;
import android.util.Log;

/**
 * 
 * A file node observer.
 * 
 * @author yrulee
 *
 */

public abstract class FileNodeObserver extends FileObserver{

	private static String TAG = FileTreeObserver.class.getSimpleName();
	
	private static boolean isSendLog = true;
	
	private File monitorNode;
	private int monitorMask;
	
	private FileTreeObserver rootObserver;
	


	
	public FileNodeObserver(FileTreeObserver rootObserver, String path) {
		super(path);
		this.rootObserver = rootObserver;
		monitorNode = new File(path);//normalize file path
		monitorMask = FileObserver.ALL_EVENTS;
	}
	
	public FileNodeObserver(FileTreeObserver rootObserver, String path, int mask) {
		super(path, mask);
		this.rootObserver = rootObserver;
		monitorNode = new File(path);//normalize file path
		monitorMask = mask;
	}

	/**
	 * 
	 * Add a FileObserver for the File path recursively.
	 * 
	 * @param folder
	 */
	
	public void addObservers() {
		addObservers(monitorNode, false);
	}
	
	/**
	 * 
	 * Add FileObserver for a file/folder recursively.
	 * 
	 * @param file file to be monitoring
	 * @param sendEvent true for using in onEvent(), false for using in manually start observing
	 */
	
	private void addObservers(File file, boolean sendEvent) {

		// How to make the already exist folder to send event? Ans: using recursively adding...

		// Ignore the level which has been already add LevelFileObserver
		
		String filePath = file.getAbsolutePath();
		
		
		if(rootObserver.getObserverMap().containsKey(filePath)){
			
			if(isSendLog)
				Log.d(TAG, "The observer is already exist for this path:" + filePath);
			
			return;
		}
		
		
		FileNodeObserver observer = new FileNodeObserver(rootObserver, filePath, monitorMask){
			@Override
			public void onNodeEvent(int event, String absolutePath) {
				
				// Do the filtering here...
				
				if(event == FileObserver.CREATE)
					return;
				
				rootObserver.onEvent(event, absolutePath);
			}
		};
		
		observer.startWatching();
		
		rootObserver.getObserverMap().put(filePath, observer);
		
		if(isSendLog)
			Log.d(TAG, "[Thread ID: " + Thread.currentThread().getId() + "] " +
					"New FileObserver has been added: " + filePath);
								
		if(sendEvent){
			// Send out the CREATE event for log compensation
			rootObserver.onEvent(FileObserver.CREATE, filePath);
		}
		
		File[] children = file.listFiles();
		
		if(children != null){
			for(File child : children){
				addObservers(child, sendEvent);
			}
		}

	}
	
	public void removeObservers() {
		
		removeObservers(monitorNode, true);

	}
	
	/**
	 * 
	 * Remove FileObserver for a file/folder recursively.
	 * 
	 * @param file the file to be removed from monitoring list
	 * @param scanChildrenFiles if true, will scan children files recursively for observer removal. otherwise, scan the observer list only (to be used in MOVE event). 
	 */
	
	private void removeObservers(File file, boolean scanChildrenFiles) {
		
		String filePath = file.getAbsolutePath();
		
		if(!rootObserver.getObserverMap().containsKey(filePath)){
			
			if(isSendLog)
				Log.d(TAG, "The observer is already removed for this path:" + filePath);
			
			return;
		}
		
		FileNodeObserver observer = rootObserver.getObserverMap().remove(filePath);
		
		if(observer != null){
			//May be it doesn't have to stop watching itself
			observer.stopWatching();
			
			if(isSendLog)
				Log.d(TAG, "Exist FileObserver has been removed: " + filePath);
		
		}else{
			
			if(isSendLog)
				Log.d(TAG, "No FileObserver has been added before. Nothing changed: " + filePath);
		}

		if(scanChildrenFiles){
			
			//Scanning files for removing
			File[] children = file.listFiles();
			
			if(children != null){
				for(File child : children){
					removeObservers(child, scanChildrenFiles);
				}
			}
		}else{
			
			removeObserversInMap(file);
		}
		

		


	}
	
	private void removeObserversInMap(File file){
		
		// Remove any children file path inside the file(folder)
		
		String filePath = file.getAbsolutePath();
		
		ArrayList<String> paths = new ArrayList<String>(rootObserver.getObserverMap().keySet());
		ArrayList<String> pathsToBeDeleted = new ArrayList<String>();
		
		for(String p : paths){
			if(p.startsWith(filePath)){
				pathsToBeDeleted.add(p);
			}
		}
		
		for(String p : pathsToBeDeleted){
			
			FileNodeObserver observer = rootObserver.getObserverMap().remove(p);
			observer.stopWatching();
			
			if(isSendLog)
				Log.d(TAG, "Exist FileObserver has been removed: " + p);
		}
		
	}
	
	@Override
	public void onEvent(int event, String path) {

		long before = System.currentTimeMillis();
		
		//inotify send strange event constant, filtering it out
		int filteredEvent = (0xfff & event);
		
		if(filteredEvent == 0 || path == null){
			return;
		}
					
		File onEventFile = new File(monitorNode, path);
		
		// We need to add newly created folder into monitor scope
		
		// List all the conditions
		
		
		switch(filteredEvent){
		
			//TODO FileObserver.ACCESS;
			//TODO FileObserver.ATTRIB;
			//TODO FileObserver.CLOSE_NOWRITE;
			//TODO FileObserver.CLOSE_WRITE;
			//TODO FileObserver.DELETE_SELF;
			//TODO FileObserver.MODIFY;
			//TODO FileObserver.OPEN;
		
	
			case FileObserver.MOVED_FROM:
			
				// Recursively remove FileObserver
				removeObservers(onEventFile, false);
			
			break;
		
			case FileObserver.MOVED_TO:
				
				// Recursively add FileObserver without CREATE event
				addObservers(onEventFile, false);
				
				break;
				
			case FileObserver.CREATE:
				
				// Recursively add FileObserver with CREATE event
				addObservers(onEventFile, true);
				
				break;
				
			case FileObserver.DELETE:
				
				// Recursively remove FileObserver
				removeObservers(onEventFile, true);
				
				break;
		
		}
					
		onNodeEvent(filteredEvent, onEventFile.getAbsolutePath());
		
		long after = System.currentTimeMillis();
		
		if(isSendLog)
			Log.d(TAG, "[onEvent] Hashcode="+this.hashCode()+
				", Event ID: "+ filteredEvent + 
				", File path: "+monitorNode.getAbsolutePath() + 
				", Event source: "+path + 
				", Time cost: " + Long.toString(after-before) + " ms");
		
	}
	
	public abstract void onNodeEvent(int event, String absolutePath);
	
}
